<template>
  <div ref="root" :style="rootStyle">
    <div :class="{ 'haloe-affix-fixed': fixed }" :style="affixStyle">
      <slot></slot>
    </div>
  </div>
</template>

<script setup>

import { computed, onMounted, ref, shallowRef, watch, watchEffect } from "vue"
import { useElementBounding, useEventListener, useWindowSize, isClient } from "@vueuse/core"
import { isString, camelize } from "@vue/shared"

const isStringNumber = (val) => {
  if (!isString(val)) {
    return false
  }
  return !Number.isNaN(Number(val))
}

const isNumber=(val)=>{
  return typeof val=== 'number'
}

const addUnit = (value, defaultUnit = 'px') => {
  if (!value) return ''
  if (isNumber(value) || isStringNumber(value)) {
    return `${value}${defaultUnit}`
  } else if (isString(value)) {
    return value
  }
  console.error('binding value must be a string or number')
}

const getStyle=(element,styleName)=>{
  if (!isClient || !element || !styleName) return ''
  let key = camelize(styleName)
  if (key === 'float') key = 'cssFloat'
  try{
    const style = (element.style)[key]
    if (style) return style
    let computed=null
    if(document.defaultView){
      computed = document.defaultView.getComputedStyle(element, '')
    }
    
    return computed ? computed[key] : ''
  }catch{
    return (element.style)[key]
  }
}

const isScroll = (el, isVertical) => {
  if (!isClient) return false

  const key = (
    {
      undefined: 'overflow',
      true: 'overflow-y',
      false: 'overflow-x',
    }
  )[String(isVertical)]
  const overflow = getStyle(el, key)
  // console.log('overflow', overflow, el, key)
  return ['scroll', 'auto', 'overlay'].some((s) => overflow.includes(s))
}

const getScrollContainer = (
  el,
  isVertical
) => {
  if (!isClient) return

  let parent = el
  while (parent) {
    if ([window, document, document.documentElement].includes(parent))
      return window
    // console.log('scrolling', isScroll(parent, isVertical))
    if (isScroll(parent, isVertical)) return parent

    parent = parent.parentNode
    // console.log('////', parent.parentNode)
  }
  return parent
}

const props=defineProps({
  /**
   * @description affix element zIndex value
   * */
  zIndex: {
    type: Number,
    default: 100,
  },
  /**
   * @description target container. (CSS selector)
   */
  target: {
    type: String,
    default: '',
  },
  /**
   * @description offset distance
   * */
  offset: {
    type: Number,
    default: 0,
  },
  /**
   * @description position of affix
   * */
  position: {
    type: String,
    values: ['top', 'bottom'],
    default: 'top',
  },
})

const emit=defineEmits(["scroll","change"])

const target = shallowRef()
const root = shallowRef()
const scrollContainer = shallowRef()
const { height: windowHeight } = useWindowSize()
const {
  height: rootHeight,
  width: rootWidth,
  top: rootTop,
  bottom: rootBottom,
  update: updateRoot,
} = useElementBounding(root, { windowScroll: false })

const targetRect = useElementBounding(target)
const fixed = ref(false)
const scrollTop = ref(0)
const transform = ref(0)

const rootStyle = computed(() => {
  return {
    height: fixed.value ? `${rootHeight.value}px` : '',
    width: fixed.value ? `${rootWidth.value}px` : '',
  }
})
const affixStyle = computed(() => {
  if (!fixed.value) return {}

  const offset = props.offset ? addUnit(props.offset) : 0
  return {
    height: `${rootHeight.value}px`,
    width: `${rootWidth.value}px`,
    top: props.position === 'top' ? offset : '',
    bottom: props.position === 'bottom' ? offset : '',
    transform: transform.value ? `translateY(${transform.value}px)` : '',
    zIndex: props.zIndex,
  }
})

const update = () => {
  if (!scrollContainer.value) return

  scrollTop.value =
    scrollContainer.value instanceof Window
      ? document.documentElement.scrollTop
      : scrollContainer.value.scrollTop || 0

  if (props.position === 'top') {
    if (props.target) {
      const difference =
        targetRect.bottom.value - props.offset - rootHeight.value
      fixed.value = props.offset > rootTop.value && targetRect.bottom.value > 0
      transform.value = difference < 0 ? difference : 0
      // console.log('translate', difference)
    } else {
      fixed.value = props.offset > rootTop.value
    }
  } else if (props.target) {
    const difference =
      windowHeight.value -
      targetRect.top.value -
      props.offset -
      rootHeight.value
    fixed.value =
      windowHeight.value - props.offset < rootBottom.value &&
      windowHeight.value > targetRect.top.value
    transform.value = difference < 0 ? -difference : 0
  } else {
    fixed.value = windowHeight.value - props.offset < rootBottom.value
  }
}

const handleScroll = () => {
  updateRoot()
  emit('scroll', {
    scrollTop: scrollTop.value,
    fixed: fixed.value,
  })
}
watch(fixed, (val) => emit('change', val))

onMounted(() => {
  if (props.target) {
    target.value =document.querySelector(props.target) ? document.querySelector(props.target): undefined
    if (!target.value) console.error( `Affix Target is not existed: ${props.target}`)
  } else {
    target.value = document.documentElement //html
  }
  scrollContainer.value = getScrollContainer(root.value, true)
  updateRoot()
})

useEventListener(scrollContainer, 'scroll', handleScroll)
watchEffect(update)

defineExpose({
  update,

  updateRoot,
})
</script>
